// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#pragma once

#include <fbl/macros.h>
#include <fbl/type_support.h>
#include <lib/async/dispatcher.h>
#include <lib/fidl/bind.h>
#include <lib/zx/channel.h>
#include <zircon/assert.h>
#include <zircon/fidl.h>

namespace fidl {
namespace internal {

template <typename>
struct MemberFunctionTraits;

template <typename R, typename T, typename... Args>
struct MemberFunctionTraits<R (T::*)(Args...)> {
    typedef T instance_type;
};

} // namespace internal

// A wrapper class which helps binding operations to a channel, and which
// helps creating C-compatible member-binding functions.
//
// This class ensures that the "void*" context value passed to |fidl_bind|
// is the same value used to dispatch to member functions.
template <typename T>
struct Binder {
    // Bind a member of a base class to a FIDL dispatch function, to allow
    // compatibility with the C bindings.
    //
    // For a FIDL function:
    //
    //   1: MyFunction(Args) -> (ReturnValue);
    //
    // The following C signature will be generated by the FIDL compiler, and is expected in a
    // server's dispatch table that wishes to implement this interface:
    //
    //   zx_status_t MyFunction(void* ctx, Args... args, fidl_txn_t* txn);
    //
    // This functionality can be implemented with this helper like so:
    //
    //   class MyClass {
    //   public:
    //       zx_status_t FunctionImplementation(Args... args, fidl_txn_t* txn) { ... }
    //   };
    //
    //   fidl_MyInterface_ops_t ops = {
    //       .MyFunction = Binder<MyClass>::BindMember<&MyClass::FunctionImplementation>
    //   };
    //
    // Which will instantiate a function with a signature matching the auto-generated "MyFunction"
    // C binding, that automatically invokes the FunctionImplementation member function, using a
    // "MyClass" instance as context.
    template <auto Fn,
              typename U = typename internal::MemberFunctionTraits<decltype(Fn)>::instance_type,
              typename... Args>
    static zx_status_t BindMember(void* ctx, Args... args) {
        static_assert(fbl::is_convertible_pointer<T*, U*>::value,
                      "Binding to method of invalid class");
        auto instance = static_cast<T*>(ctx);
        return (instance->*Fn)(static_cast<decltype(args)&&>(args)...);
    }

    // A utility function which simplifies binding methods of derived methods to FIDL
    // dispatch functions.
    //
    // A typical use case would look like the following:
    //
    // FIDL:
    //
    //   1: MyFunction(Args) -> (ReturnValue);
    //
    // C++:
    //
    //   class MyClass {
    //   public:
    //       zx_status_t FunctionImplementation(Args... args, fidl_txn_t* txn) {
    //           ...;
    //       }
    //
    //       zx_status_t Bind(async_dispatcher_t* dispatcher, zx::channel channel) {
    //           static constexpr Interface_ops_t kOps = {
    //               .MyFunction = Binder<MyClass>::BindMember<&MyClass::FunctionImplementation>,
    //           };
    //           return Binder<MyClass>::BindOps<Dispatch>(dispatcher, channel, this, &kOps);
    //       }
    //   }
    //
    //   ...
    //
    //   MyClass instance;
    //   instance.Bind(dispatcher, channel);
    template <auto Dispatch, typename Ops>
    static zx_status_t BindOps(async_dispatcher_t* dispatcher, zx::channel channel,
                               T* ctx, const Ops* ops) {
        static_assert(fbl::is_same<decltype(Dispatch),
                                   zx_status_t (*)(void*, fidl_txn_t*, fidl_msg_t*, const Ops* ops)
                                  >::value, "Invalid dispatch function");
        return fidl_bind(dispatcher, channel.release(),
                         reinterpret_cast<fidl_dispatch_t*>(Dispatch), ctx, ops);
    }
};

// Wrapper class around |fidl_async_txn_t|. This allows a transaction to be
// initialized, moved, reset, and completed without unintentionally "double completing"
// or "forgetting to complete".
class AsyncTransaction {
public:
    DISALLOW_COPY_AND_ASSIGN_ALLOW_MOVE(AsyncTransaction);

    AsyncTransaction() : txn_(nullptr) {}
    explicit AsyncTransaction(fidl_txn_t* txn) : txn_(fidl_async_txn_create(txn)) {}
    explicit AsyncTransaction(AsyncTransaction&& other) : txn_(other.release()) {}
    AsyncTransaction& operator=(AsyncTransaction&& other) {
        Reinitialize(other.release());
        return *this;
    }

    ~AsyncTransaction() {
        Reinitialize();
    }

    // Acquires a reference to the |fidl_txn_t| backing this txn object.
    // This reference will be invalidated if any non-const operations are called
    // on |AsyncTransaction|.
    //
    // Should not be called if the underlying transaction is invalid.
    fidl_txn_t* Transaction() const {
        ZX_DEBUG_ASSERT(HasTransaction());
        return fidl_async_txn_borrow(txn_);
    }

    // Completes the transaction and rebinds the underlying channel against the binding.
    //
    // Causes the transaction to become invalid.
    //
    // Should not be called if the underlying transaction is invalid.
    zx_status_t Rebind() {
        ZX_DEBUG_ASSERT(HasTransaction());
        return fidl_async_txn_complete(release(), true);
    }

    // Completes the current transaction, if one exists, and causes |AsyncTransaction| to
    // track a new |txn|.
    //
    // If |txn| is nullptr, this function causes the underlying transaction to become invalid.
    void Reset(fidl_txn_t* txn = nullptr) {
        fidl_async_txn_t* async_txn = txn ? fidl_async_txn_create(txn) : nullptr;
        Reinitialize(async_txn);
    }

private:
    bool HasTransaction() const {
        return txn_ != nullptr;
    }

    __attribute__((warn_unused_result)) fidl_async_txn_t* release() {
        fidl_async_txn_t* txn = txn_;
        txn_ = nullptr;
        return txn;
    }

    void Reinitialize(fidl_async_txn_t* txn = nullptr) {
        if (HasTransaction()) {
            fidl_async_txn_complete(release(), false);
        }
        txn_ = txn;
    }

    fidl_async_txn_t* txn_;
};

} // namespace fidl
